using System;
using System.Collections.Generic;
using System.Reflection;

namespace NFreeMarker.Ext.Beans
{
    /**
     * 
     * @author Attila Szegedi
     * @version $Id: $
     * @param 
     */
    sealed class ClassString
    {
        //private static readonly Type BIGDECIMAL_CLASS = BigDecimal.class;
        //private static readonly Type NUMBER_CLASS = Number.class;

        private readonly Type[] _classes;
        
        internal ClassString(object[] objects)
        {
            int l = objects.Length;
            _classes = new Type[l];
            for (int i = 0; i < l; ++i)
            {
                object obj = objects[i];
                _classes[i] = obj == null ? MethodUtilities.ObjectClass : obj.GetType();
            }
        }
        
        Type[] GetClasses()
        {
            return _classes;
        }
        
        public override int GetHashCode()
        {
            int hash = 0;
            foreach (Type type in _classes)
            {
                hash ^= type.GetHashCode();
            }
            return hash;
        }
        
        public override bool Equals(object o)
        {
            if (o is ClassString)
            {
                var cs = (ClassString) o;
                if (cs._classes.Length != _classes.Length)
                {
                    return false;
                }
                for (int i = 0; i < _classes.Length; ++i)
                {
                    if (cs._classes[i] != _classes[i])
                    {
                        return false;
                    }
                }
                return true;
            }
            return false;
        }
        
        private enum MoreSpecificType
        {
            MoreSpecific,
            LessSpecific,
            Indeterminate
        }

        
        internal object GetMostSpecific(IEnumerable<MemberInfo> methods, bool varArg)
        {
            LinkedList<MemberInfo> applicables = GetApplicables(methods, varArg);
            if (applicables.Count == 0)
            {
                return OverloadedMethod.NoSuchMethod;
            }
            if (applicables.Count == 1)
            {
                return applicables.First;
            }
            var maximals = new LinkedList<MemberInfo>();
            foreach (MemberInfo applicable in applicables)
            {
                //Type[] appArgs = MethodUtilities.GetParameterTypes(applicable);
                //bool lessSpecific = false;
                //for (Iterator maximal = maximals.iterator(); maximal.hasNext();)
                //{
                //    var max = (MemberInfo) maximal.next();
                //    Type[] maxArgs = MethodUtilities.GetParameterTypes(max);
                //    switch (MoreSpecific(appArgs, maxArgs, varArg))
                //    {
                //        case MoreSpecificType.MoreSpecific:
                //        {
                //            maximal.remove();
                //            break;
                //        }
                //        case MoreSpecificType.LessSpecific:
                //        {
                //            lessSpecific = true;
                //            break;
                //        }
                //    }
                //}
                //if (!lessSpecific)
                //{
                //    maximals.AddLast(applicable);
                //}

                throw new NotImplementedException();
            }
            if (maximals.Count > 1)
            {
                return OverloadedMethod.AmbiguousMethod;
            }
            return maximals.First;
        }
        
        private static MoreSpecificType MoreSpecific(Type[] c1, Type[] c2, bool varArg)
        {
            bool c1MoreSpecific = false;
            bool c2MoreSpecific = false;
            int cl1 = c1.Length;
            int cl2 = c2.Length;
            //assert varArg || cl1 == cl2;
            for(int i = 0; i < cl1; ++i) {
                Type class1 = GetClass(c1, cl1, i, varArg);
                Type class2 = GetClass(c2, cl2, i, varArg);
                if(class1 != class2)
                {
                    c1MoreSpecific = 
                        c1MoreSpecific ||
                        MethodUtilities.IsMoreSpecific(class1, class2);
                    c2MoreSpecific = 
                        c2MoreSpecific ||
                        MethodUtilities.IsMoreSpecific(class2, class1);
                }
            }
            if (c1MoreSpecific)
            {
                if (c2MoreSpecific)
                {
                    return MoreSpecificType.Indeterminate;
                }
                return MoreSpecificType.MoreSpecific;
            }
            if (c2MoreSpecific)
            {
                return MoreSpecificType.LessSpecific;
            }
            return MoreSpecificType.Indeterminate;
        }
        
        private static Type GetClass(Type[] classes, int l, int i, bool varArg)
        {
            //return varArg && i >= l - 1 ? classes[l - 1].GetComponentType() : classes[i];
            throw new NotImplementedException();
        }
        
        /**
         * Returns all methods that are applicable to actual
         * parameter classes represented by this ClassString object.
         */
        LinkedList<MemberInfo> GetApplicables(IEnumerable<MemberInfo> methods, bool varArg)
        {
            var list = new LinkedList<MemberInfo>();
            foreach (MemberInfo member in methods)
            {
                if (IsApplicable(member, varArg))
                {
                    list.AddLast(member);
                }
            }
            return list;
        }
        
        /**
         * Returns true if the supplied method is applicable to actual
         * parameter classes represented by this ClassString object.
         * 
         */
        private bool IsApplicable(MemberInfo member, bool varArg)
        {
            Type[] formalTypes = MethodUtilities.GetParameterTypes(member);
            int cl = _classes.Length;
            int fl = formalTypes.Length - (varArg ? 1 : 0);
            if (varArg)
            {
                if (cl < fl)
                {
            	    return false;
                }
            }
            else
            {
                if (cl != fl)
                {
            	    return false;
                }
            }
            for (int i = 0; i < fl; ++i)
            {
                if (!IsMethodInvocationConvertible(formalTypes[i], _classes[i]))
                {
                    return false;
                }
            }
            if (varArg)
            {
                //Type varArgType = formalTypes[fl].GetComponentType();
                //for (int i = fl; i < cl; ++i)
                //{
                //    if (!IsMethodInvocationConvertible(varArgType, _classes[i]))
                //    {
                //        return false;
                //    }
                //}
                throw new NotImplementedException();
            }
            return true;
        }

        /**
         * Determines whether a type represented by a class object is
         * convertible to another type represented by a class object using a 
         * method invocation conversion, treating object types of primitive 
         * types as if they were primitive types (that is, a Boolean actual 
         * parameter type matches boolean primitive formal type). This behavior
         * is because this method is used to determine applicable methods for 
         * an actual parameter list, and primitive types are represented by 
         * their object duals in reflective method calls.
         * @param formal the formal parameter type to which the actual 
         * parameter type should be convertible
         * @param actual the actual parameter type.
         * @return true if either formal type is assignable from actual type, 
         * or formal is a primitive type and actual is its corresponding object
         * type or an object type of a primitive type that can be converted to
         * the formal type.
         */
        static bool IsMethodInvocationConvertible(Type formal, Type actual)
        {
            // Check for identity or widening reference conversion
            if (formal.IsAssignableFrom(actual))
            {
                return true;
            }
            // Check for boxing with widening primitive conversion. Note that 
            // actual parameters are never primitives.
            if (formal.IsPrimitive)
            {
                if (formal == typeof (bool))
                    return actual == typeof (bool);
                if (formal == typeof (char))
                    return actual == typeof (char);
                if (formal == typeof (byte) && actual == typeof (byte))
                    return true;
                if (formal == typeof (short) &&
                   (actual == typeof (short) || actual == typeof (byte)))
                    return true;
                if (formal == typeof (int) && 
                   (actual == typeof (int) || actual == typeof (short) || 
                    actual == typeof (short)))
                    return true;
                if (formal == typeof (long) && 
                   (actual == typeof (long) || actual == typeof (int) || 
                    actual == typeof (short) || actual == typeof (byte)))
                    return true;
                if (formal == typeof (float) && 
                   (actual == typeof (float) || actual == typeof (long) || 
                    actual == typeof (int) || actual == typeof (short) || 
                    actual == typeof (byte)))
                    return true;
                if (formal == typeof (double) && 
                   (actual == typeof (double) || actual == typeof (float) || 
                    actual == typeof (long) || actual == typeof (int) || 
                    actual == typeof (short) || actual == typeof (byte)))
                    return true; 
                // Special case for BigDecimals as we deem BigDecimal to be
                // convertible to any numeric type - either object or primitive.
                // This can actually cause us trouble as this is a narrowing 
                // conversion, not widening. 
                return IsBigDecimalConvertible(formal, actual);
            }
            return false;
        }
        
        private static bool IsBigDecimalConvertible(Type formal, Type actual)
        {
            // BigDecimal 
            // TODO: if(BIGDECIMAL_CLASS.isAssignableFrom(actual))
            //{
            //    if(NUMBER_CLASS.isAssignableFrom(formal))
            //    {
            //        return true;
            //    }
            //    if(formal.isPrimitive() && 
            //       formal != Boolean.TYPE && formal != Character.TYPE)
            //    {
            //       return true;
            //    }
            //}
            return false;
        }
    }
}